// electron
const { app, BrowserWindow, Tray, ipcMain, shell, Menu, dialog, screen, session, nativeImage } = require('electron');
const isDev = require('electron-is-dev');
// node
const os = require('os')
const path = require('path');
const fs = require('fs');
const { Readable } = require('stream');
// others
const { Parser } = require('m3u8-parser');
const dateFormat = require('dateformat');
const download = require('download');
const got = require('got');
const ffmpeg = require('fluent-ffmpeg');
const Aria2 = require('aria2');
const forever = require('forever-monitor');
const { HttpProxyAgent, HttpsProxyAgent } = require('hpagent');
const lodash = require('lodash');
const ffmpegPath = require('ffmpeg-static').replace('app.asar', 'app.asar.unpacked');
// inner
const { stringify } = require('../common/json');
const { DownloadStatus, isFinishStatus } = require('../common/DownloadStatus');
const { DownloadStateManager } = require('./downloader/DownloadStateManager');
const M3U8Downloader = require('./downloader/M3U8Downloader');
const MP4Downloader = require('./downloader/MP4Downloader');
const loadExtensions = require('./loadExtensions');
const logger = require('./utils/logger');
const nconf = require('./setting');
const registerShortcut = require('./localShortcut');
// execute immediately
require('./init');
require('./contextMenu');
require('./handleErrors');
require('./sniffViewEvents');

const package_self = require('../package.json');
const appInfo = package_self;

let isdelts = true;
let mainWindow = null;
let playerWindow = null;
let tray = null;
let AppTitle = 'M3U8-Downloader'
let firstHide = true;

const userDataDir = app.getPath('userData');
const pathVideoDB = path.join(userDataDir, 'config_videos.json');
const rootDir = path.join(__dirname, '..');
const aria2Dir = path.join(app.getAppPath(), "render", "aria2", process.platform);
const aria2_app = path.join(aria2Dir, "aria2c.exe");
const aria2_config = path.join(aria2Dir, "aria2.conf");

let aria2Client = null;
let aria2Server = null;
let proxy_agent = null;
let pathDownloadDir;
let titleFilter = '';
let downloadTsWithHeader = nconf.get('downloadTsWithHeader') ?? true;
const downloadStateManager = new DownloadStateManager(pathVideoDB);
const taskNameFormatReg = /["“”，\.。\|\/\\\s\*:;\?<>]/g;

app.allowRendererProcessReuse = true;

const httpTimeout = {
    socket: 30000,
    request: 30000,
    response: 60000
};

const defaultUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36';
const finalUA = nconf.get('requestUA') || defaultUA;
const referer = `https://tools.heisir.cn/M3U8Soft-Client?v=${package_self.version}`;

logger.info(`版本信息：node-${process.versions.node}，electron-${process.versions.electron}`);

// 发送消息到主窗口
let seq = 0;
function sendMessageToMainWindow(message, data) {
    if (!message) {
        console.error('message 参数不能为空');
    }
    mainWindow.webContents.send(message, { ...data, __seq__: ++seq });
}

function transformConfig(config) {
    const result = []
    for (const [k, v] of Object.entries(config)) {
        if (v !== '') {
            result.push(`--${k}=${v}`)
        }
    }
    return result
}

console.log(`\n\n----- ${appInfo.name} | v${appInfo.version} | ${os.platform()} -----\n\n`)

function createWindow() {
    // 创建浏览器窗口
    let _workAreaSize = screen.getDisplayNearestPoint(screen.getCursorScreenPoint()).workAreaSize;
    mainWindow = new BrowserWindow({
        width: Math.min(_workAreaSize.width * 0.6, 1440),
        height: Math.min(_workAreaSize.height * 0.7, 900),
        minWidth: 954,
        minHeight: 600,
        center: true,
        frame: true,
        resizable: true,
        webPreferences: {
            nodeIntegration: true,
            spellcheck: false,
            contextIsolation: false,
            webviewTag: true,
            enableRemoteModule: true,
        },
        icon: path.join(rootDir, 'render/icon/logo.png'),
        alwaysOnTop: false,
        hasShadow: true,
        title:`${AppTitle} ${package_self.version}`
    });
    registerShortcut(mainWindow);
    mainWindow.setMenu(null);
    mainWindow.loadFile(path.join(rootDir, 'render/main.html'));
    if (process.argv.indexOf('--devtools') !== -1) {
        mainWindow.openDevTools();
        mainWindow.webContents.on('did-finish-load', () => {
            sendMessageToMainWindow('open-devtools');
        });
    }
    mainWindow.on('closed', () => {
        mainWindow = null;
        downloadStateManager._saveToDB();
    });
    mainWindow.webContents.on('dom-ready', (e) => {
        sendMessageToMainWindow('message', {
            version: package_self.version,
            config_save_dir: pathDownloadDir,
            config_title_filter: titleFilter,
            config_custom_url_suffix: nconf.get('CustomUrlSuffix'),
            config_suggestDomainList: nconf.get('suggestDomainList'),
            config_requestUA: nconf.get('requestUA'),
            config_ffmpeg: ffmpegPath,
            config_proxy: nconf.get('config_proxy'),
            config_player: nconf.get('config_player'),
            config_downloadTsWithHeader: downloadTsWithHeader,
            videoDatas: downloadStateManager.videos,
            platform: process.platform
        });
        // mainWindow.webContents.setTitle(`${AppTitle} ${package_self.version}`)
    })
    mainWindow.webContents.setWindowOpenHandler((details) => {
        shell.openExternal(details.url);
        return { action: 'deny' };
    });
}

let currentPlaySrc;
let currentPlayHeaders;
function createPlayerWindow(src, headers) {
    currentPlaySrc = src;
    currentPlayHeaders = typeof headers === 'string' ? JSON.parse(headers || '{}') : (headers || {});
    if (playerWindow == null) {
        // 创建浏览器窗口
        // let _workAreaSize = screen.getDisplayNearestPoint(screen.getCursorScreenPoint()).workAreaSize;
        const mainWindowSize = mainWindow.getContentSize();
        playerWindow = new BrowserWindow({
            width: Math.min(mainWindowSize[0], 1440),
            height: Math.min(mainWindowSize[1], 900),
            skipTaskbar: false,
            transparent: false,
            // frame: (process.platform == 'darwin'),
            resizable: true,
            webPreferences: {
                nodeIntegration: true,
                contextIsolation: false,
            },
            icon: path.join(rootDir, 'render/icon/logo.png'),
            alwaysOnTop: false,
            hasShadow: true,
            parent: mainWindow
        });
        registerShortcut(playerWindow);
        playerWindow.setMenu(null)
        playerWindow.on('closed', () => playerWindow = null);
        playerWindow.loadFile(path.join(rootDir, 'render/player.html'));
        playerWindow.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => {
            if (details.url === currentPlaySrc) {
                const headers = details.requestHeaders || {};
                Object.assign(headers, currentPlayHeaders);
                details.requestHeaders = headers;
            }
            callback({ requestHeaders: details.requestHeaders, cancel: false });
        });
        playerWindow.webContents.on('dom-ready', (e) => {
            playerWindow.webContents.send('message', {
                platform: process.platform,
                playsrc: src,
                headers,
            });
        });
        if (process.argv.indexOf('--devtools') !== -1) {
            playerWindow.openDevTools();
        }
        return;
    }
    playerWindow.webContents.send('message', {
        playsrc: src,
        headers,
    });
}

// 9999.9999.9999 > 1.1.1 最高支持4位版本对比。  1.2.1 > 1.2.0   1.3 > 1.2.9999
function str2float(v) {
    let va = v.split('.', 4);
    if (!va) return -1;
    let _r = 0;
    let base = 100000000.0;
    va.forEach(k => _r += (base * k), base /= 10000);
    return _r;
}

let _updateInterval;
async function checkUpdate() {
    const {
        body
    } = await got("https://tools.heisir.cn/HLSDownload/package.json").catch(e => {
        console.error(error);
        logger.error(e);
    });
    if (!body) return;
    try {
        let _package = JSON.parse(body);
        if (str2float(_package.version) <= str2float(package_self.version))
            return;

        _updateInterval && (clearInterval(_updateInterval), _updateInterval = null);

        if (dialog.showMessageBoxSync(mainWindow, {
            type: 'question',
            buttons: ["Yes", "No"],
            message: `检测到新版本(${_package.version})，是否要打开升级页面，下载最新版`
        }) == 0) {
            shell.openExternal("https://tools.heisir.cn/HLSDownload/download.html");
            return;
        }
    } catch (error) {
        console.error(error);
        logger.error(error);
    }
}

const excludeResourceType = ['image', 'script', 'stylesheet', 'xhr', 'subFrame', 'mainFrame'];
function logWebResource(details) {
    if (details.url.startsWith('http') && excludeResourceType.indexOf(details.resourceType) === -1) {
        // const url = new URL(details.url);
        console.debug(`\nResource type: ${details.resourceType}, URL: ${details.url.substring(0, 64)}`);
        if (details.responseHeaders) {
            console.debug(`Headers: ${JSON.stringify(details.responseHeaders)}\n`);
        }
    }
}

function webRequestHead(details, callback) {
    // 设置 ua
    const ua = finalUA;
    details.requestHeaders['User-Agent'] = ua;

    const id = details.webContentsId;
    if (!id || id == mainWindow.webContents.id) {
        callback({ requestHeaders: details.requestHeaders, cancel: false });
        return;
    }
    const url = details.url;
    const resourceType = details.resourceType;
    const headers = { ...details.requestHeaders };
    delete headers['Range'];
    delete headers['range'];
    const headersStr = JSON.stringify(headers);
    if (excludeResourceType.indexOf(resourceType) === -1) {
        // 保存headers，用于下载在响应时发现的资源
        sendMessageToMainWindow('message', {
            requestHeaders: { [url]: headersStr },
        });
    }
    
    let videoType;
    const reg = /^http.*\.((mp4)|(m3u8)|(flv)|(mp3)|(mpd)|(wav))(\?|$)/;
    if (reg.test(url)) {
        let [_null, _type] = url.match(reg);
        videoType = _type;
    } else {
        // 自定义
        const customUrlSuffix = nconf.get('CustomUrlSuffix');
        if (customUrlSuffix) {
            try {
                const customReg = new RegExp(customUrlSuffix, 'g');
                if (customReg.test(url)) {
                    // 暂时只支持 m3u8
                    videoType = 'm3u8';
                }
            } catch (e) {
                console.error('自定义 url 后缀匹配正则表达式异常', e, customUrlSuffix);
            }
        }
    }
    if (videoType) {
        let _item = {
            type: videoType.toUpperCase(),
            url,
            headers: headersStr,
        }
        sendMessageToMainWindow('message',{ browserVideoItem: _item });
    }

    callback({ requestHeaders: details.requestHeaders, cancel: false });
};

function webRequestRsp(details) {
    const id = details.webContentsId;
    if (!id || id == mainWindow.webContents.id) {
        return
    }
    // /http.*\.((mp4)|(m3u8)|(flv)|(mp3)|(mpd)|(wav))(\?|$)/.test(details.url) &&
    //     console.log("rsp\t" + details.url);
    // "content-disposition":[" inline; filename=\"112967.mp4\""],"content-type":["video/mp4"]
    const headers = details.responseHeaders || {};
    const contentType = headers['Content-Type'] || headers['content-type'];
    const videoTypeMap = {
        'application/x-mpegurl': 'mp4',
        'video/x-flv': 'flv',
        'application/vnd.apple.mpegurl': 'mp4', // 
        'video/mp4': 'mp4', // mp4
    };
    let type;
    if (Array.isArray(contentType)) {
        while (!type && contentType.length > 0) {
            type = videoTypeMap[contentType.shift().toLowerCase()];
        }
    } else {
        type = videoTypeMap[contentType];
    }
    if (!type) {
        return;
    }
    const { url } = details;
    sendMessageToMainWindow('message',{ browserVideoItem: { type, url } });
}

function webRequestCompleted(details) {
    if (details.resourceType !== 'mainFrame') {
        return;
    }
    console.log(details.url, details.webContents.getTitle());
}

function exit() {
    // 停止所有下载任务
    downloadStateManager.stopAllTasks();
    
    // 关闭窗口和退出
    aria2Server && aria2Server.stop();
    playerWindow && playerWindow.close();
    mainWindow && mainWindow.close();
    setTimeout(app.quit.bind(app), 1000);
}


app.on('ready', async () => {

    // 单例应用程序
    if (!app.requestSingleInstanceLock()) {
        app.quit();
        return
    }
    loadExtensions();
    app.on('second-instance', (event, argv, cwd) => {
        if (mainWindow) {
            if (mainWindow.isMinimized()) {
                mainWindow.restore()
            } else if (mainWindow.isVisible()) {
                mainWindow.focus()
            } else {
                mainWindow.show()
                mainWindow.focus()
            }
        } else {
            app.quit();
        }
    });
    createWindow();
    let iconImg = nativeImage.createFromPath(path.join(rootDir, 'render/icon/logo.png'));
    tray = new Tray(iconImg.resize({ width: 20, height: 20 }));
    tray.setToolTip(AppTitle);
    tray.on("double-click", () => {
        mainWindow && mainWindow.show();
    });

    const contextMenu = Menu.buildFromTemplate([{
        label: '显示窗口',
        type: 'normal',
        click: () => {
            mainWindow.show();
        }
    },
    {
        type: 'separator'
    },
    {
        label: '退出',
        type: 'normal',
        click: () => {
            exit();
        }
    }
    ]);
    tray.setContextMenu(contextMenu);

    pathDownloadDir = nconf.get('SaveVideoDir');
    titleFilter = nconf.get('TitleFilter');

    // 代理
    const config_proxy = nconf.get('config_proxy');
    if (config_proxy) {
        const httpProxy = new HttpProxyAgent({
            keepAlive: true,
            keepAliveMsecs: 1000,
            maxSockets: 256,
            maxFreeSockets: 256,
            scheduling: 'lifo',
            proxy: config_proxy
        });
        proxy_agent = {
            http: httpProxy,
            https: httpProxy
        }
        session.defaultSession.setProxy({
            mode: 'fixed_servers',
            proxyRules: config_proxy,
        });
    }

    //session.defaultSession.webRequest.onBeforeRequest(webRequestReq);
    session.defaultSession.webRequest.onBeforeSendHeaders(webRequestHead);
    session.defaultSession.webRequest.onResponseStarted(webRequestRsp);
    // session.defaultSession.webRequest.onCompleted({ urls: ['http://*/*', 'https://*/*'] }, webRequestCompleted)
    // session.defaultSession.webRequest.onHeadersReceived

    //百度统计代码
    false && (async () => {
        try {
            checkUpdate();
            _updateInterval = setInterval(checkUpdate, 600000);

            let HMACCOUNT = nconf.get('HMACCOUNT');
            if (!HMACCOUNT) HMACCOUNT = '';
            const {
                headers
            } = await got("http://hm.baidu.com/hm.js?300991eff395036b1ba22ae155143ff3", {
                headers: {
                    "Referer": referer,
                    "Cookie": "HMACCOUNT=" + HMACCOUNT
                }
            });
            try {
                HMACCOUNT = headers['set-cookie'] && headers['set-cookie'][0].match(/HMACCOUNT=(.*?);/i)[1];
                if (HMACCOUNT) {
                    nconf.set('HMACCOUNT', HMACCOUNT);
                    nconf.save();
                }
            } catch (error_) {
                console.error(error_);
                logger.error(error_)
            }
            await got(`http://hm.baidu.com/hm.gif?hca=${HMACCOUNT}&cc=1&ck=1&cl=24-bit&ds=1920x1080&vl=977&ep=6621%2C1598&et=3&ja=0&ln=zh-cn&lo=0&lt=${(new Date().getTime() / 1000)}&rnd=0&si=300991eff395036b1ba22ae155143ff3&v=1.2.74&lv=3&sn=0&r=0&ww=1920&u=${encodeURIComponent(referer)}`, {
                headers: {
                    "Referer": referer,
                    "Cookie": "HMACCOUNT=" + HMACCOUNT
                }
            });
            await got(`http://hm.baidu.com/hm.gif?cc=1&ck=1&cl=24-bit&ds=1920x1080&vl=977&et=0&ja=0&ln=zh-cn&lo=0&rnd=0&si=300991eff395036b1ba22ae155143ff3&v=1.2.74&lv=1&sn=0&r=0&ww=1920&ct=!!&tt=M3U8Soft-Client`, {
                headers: {
                    "Referer": referer,
                    "Cookie": "HMACCOUNT=" + HMACCOUNT
                }
            });
            logger.info("call baidu-tong-ji end.");
        } catch (error) {
            console.error(error);
            logger.error(error)
        }
    })();
    return;

    const EMPTY_STRING = '';
    const systemConfig = {
        'all-proxy': EMPTY_STRING,
        'allow-overwrite': false,
        'auto-file-renaming': true,
        'check-certificate': false,
        'continue': false,
        'dir': app.getPath('downloads'),
        'max-concurrent-downloads': 120,
        'max-connection-per-server': 5,
        'max-download-limit': 0,
        'max-overall-download-limit': 0,
        'max-overall-upload-limit': '256K',
        'min-split-size': '1M',
        'no-proxy': EMPTY_STRING,
        'pause': true,
        'rpc-listen-port': 16801,
        'rpc-secret': EMPTY_STRING,
        'seed-ratio': 1,
        'seed-time': 60,
        'split': 10,
        'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36Transmission/2.94'
    }

    let cmds = [aria2_app, `--conf-path=${aria2_config}`];
    cmds = [...cmds, ...transformConfig(systemConfig)];
    logger.debug(cmds.join(' '));

    let instance = forever.start(cmds, {
        max: 10,
        parser: function (command, args) {
            logger.debug(command, args);
            return {
                command: command,
                args: args
            }
        },
        silent: false
    });
    instance.on('start', function (process, data) {
        let aria2 = new Aria2({
            port: 16801
        });
        aria2.open();
        aria2.on('close', (e) => {
            console.log('----aria2 connect close----');
            setTimeout(() => aria2.open(), 100);
        });
        aria2.on("onDownloadComplete", downloadComplete);
        aria2Client = aria2;

        setInterval(() => {
            aria2Client.call('getGlobalStat').then((result) => {
                if (result && result['downloadSpeed']) {
                    var _speed = '';
                    var speed = parseInt(result['downloadSpeed']);
                    _speed = (speed < 1024 * 1024) ? Math.round(speed / 1024) + ' KB/s' : (speed / 1024 / 1024).toFixed(2) + ' MiB/s'
                    sendMessageToMainWindow('message', { downloadSpeed: _speed });
                }
            });
        }, 1500);
    });
    aria2Server = instance;
});

function downloadComplete(e) {
    console.log('---- aria2 downloadComplete ----');
    var gid = e[0]['gid'];

    console.log(gid);
}

// 当全部窗口关闭时退出。
app.on('window-all-closed', async () => {

    console.log('window-all-closed')
    let HMACCOUNT = nconf.get('HMACCOUNT');
    HMACCOUNT && await got(`http://hm.baidu.com/hm.gif?cc=1&ck=1&cl=24-bit&ds=1920x1080&vl=977&et=0&ja=0&ln=zh-cn&lo=0&rnd=0&si=300991eff395036b1ba22ae155143ff3&v=1.2.74&lv=1&sn=0&r=0&ww=1920&ct=!!&tt=M3U8Soft-Client`, {
        headers: {
            "Referer": referer,
            "Cookie": "HMACCOUNT=" + HMACCOUNT
        }
    });

    // 在 macOS 上，除非用户用 Cmd + Q 确定地退出，
    // 否则绝大部分应用及其菜单栏会保持激活。
    if (process.platform !== 'darwin') {
        tray && tray.destroy();
        tray = null;
        app.quit();
    };
})

app.on('activate', () => {
    // 在macOS上，当单击dock图标并且没有其他窗口打开时，
    // 通常在应用程序中重新创建一个窗口。
    if (mainWindow === null) {
        createWindow()
    } else {
        mainWindow.show();
    }
})

ipcMain.on('exit', exit);

ipcMain.on("hide-windows", function () {
    mainWindow && mainWindow.hide(), (firstHide && tray && (tray.displayBalloon({
        icon: path.join(rootDir, 'render/icon/logo-512.png'),
        title: "提示",
        content: "我隐藏到这里了哦，双击我显示主窗口！"
    }), firstHide = false));
});

ipcMain.on('open-log-dir', function (event, arg) {
    showDirInExploer(path.join(userDataDir, 'logs'))
});

ipcMain.on('task-clear', async function (event, object) {
    // 停止所有下载任务
    downloadStateManager.stopAllTasks();
    // 清除所有视频记录
    downloadStateManager.videos = [];
    downloadStateManager._saveToDB();
});

ipcMain.on('task-add', async function (event, object) {
    logger.info(stringify(object));
    let hlsSrc = object.url;
    let _headers = {};
    if (object.headers) {
        let __ = object.headers.match(/(.*?): ?(.*?)(\n|\r|$)/g);
        __ && __.forEach((_) => {
            let ___ = _.match(/(.*?): ?(.*?)(\n|\r|$)/i);
            ___ && (_headers[___[1]] = ___[2]);
        });
    }

    let mes = hlsSrc.match(/^https?:\/\/[^/]*/);
    let _hosts = '';
    if (mes && mes.length >= 1) {
        _hosts = mes[0];

        if (_headers['Origin'] == null && _headers['origin'] == null) {
            _headers['Origin'] = _hosts;
        }
        if (_headers['Referer'] == null && _headers['referer'] == null) {
            _headers['Referer'] = _hosts;
        }
    }

    object.headers = _headers;

    let info = '解析资源失败！';
    let code = -1;

    let parser = new Parser();
    if (/^file:\/\/\//g.test(hlsSrc)) {
        parser.push(fs.readFileSync(hlsSrc.replace(/^file:\/\/\//g, '')));
        parser.end();
    } else {
        for (let index = 0; index < 3; index++) {
            let response = await got(hlsSrc, {
                headers: _headers,
                timeout: httpTimeout,
                rejectUnauthorized: false,
                agent: proxy_agent
            }).catch(e => logger.error(`${hlsSrc}获取异常：${e}`));
            
            if (response && response.body != null && response.body != '') {
                parser.push(response.body);
                parser.end();

                if (parser.manifest.segments.length == 0 && parser.manifest.playlists && parser.manifest.playlists.length && parser.manifest.playlists.length == 1) {
                    let uri = parser.manifest.playlists[0].uri;
                    if (!uri.startsWith('http')) {
                        hlsSrc = uri[0] == '/' ? (hlsSrc.substr(0, hlsSrc.indexOf('/', 10)) + uri) :
                            (hlsSrc.replace(/\/[^\/]*((\?.*)|$)/, '/') + uri);
                    }
                    else {
                        hlsSrc = uri;
                    }
                    object.url = hlsSrc;
                    parser = new Parser();
                    continue;
                }
                break;
            }
        }
    }

    let count_seg = parser.manifest.segments.length;
    if (count_seg > 0) {
        code = 0;
        if (parser.manifest.endList) {
            let duration = 0;
            parser.manifest.segments.forEach(segment => {
                duration += segment.duration;
            });
            info = `点播资源解析成功，有 ${count_seg} 个片段，时长：${formatTime(duration)}，即将开始缓存...`;
            startDownload(object);
        } else {
            info = `直播资源解析成功，即将开始缓存...`;
            startDownloadLive(object);
        }
    } else if (parser.manifest.playlists && parser.manifest.playlists.length && parser.manifest.playlists.length >= 1) {
        code = 1;
        event.sender.send('task-add-reply', {
            code: code,
            message: '',
            playlists: parser.manifest.playlists
        });
        return;
    }
    event.sender.send('task-add-reply', {
        code: code,
        message: info
    });
});


ipcMain.on('task-add-muti', async function (event, object) {
    logger.info(object);
    let m3u8_urls = object.m3u8_urls;
    let _headers = {};
    if (object.headers) {
        let __ = object.headers.match(/(.*?): ?(.*?)(\n|\r|$)/g);
        __ && __.forEach((_) => {
            let ___ = _.match(/(.*?): ?(.*?)(\n|\r|$)/i);
            ___ && (_headers[___[1]] = ___[2]);
        });
    }

    let info = '解析资源失败！';
    let code = -1;
    let iidx = 0;
    m3u8_urls.split(/\r|\n/g).forEach(urls => {
        if (urls != '') {
            let _obj = {
                url: '',
                headers: object.headers,
                myKeyIV: '',
                taskName: '',
                taskIsDelTs: object.taskIsDelTs,
                url_prefix: ''
            };
            if (/-{4}/.test(urls)) {
                let __ = urls.split('----');
                if (__ && __.length >= 2) {
                    if (__[0]) {
                        _obj.url = __[0];
                        if (__[1]) {
                            _obj.taskName = __[1];
                        }
                    }
                }
            } else {
                _obj.url = urls;
            }

            if (_obj.url) {

                let mes = _obj.url.match(/^https?:\/\/[^/]*/);
                let _hosts = '';
                if (mes && mes.length >= 1) {
                    _hosts = mes[0];

                    if (_headers['Origin'] == null && _headers['origin'] == null) {
                        _headers['Origin'] = _hosts;
                    }
                    if (_headers['Referer'] == null && _headers['referer'] == null) {
                        _headers['Referer'] = _hosts;
                    }
                }

                _obj.headers = _headers;

                startDownload(_obj, iidx);
                iidx = iidx + 1;
            }
        }
    })
    info = `批量添加成功，正在下载...`;
    event.sender.send('task-add-reply', {
        code: 0,
        message: info
    });
});

function initDownloader(options, isNewTask = true) {
    const { id, url, headers, dir, taskName, keyIV, deleteSegments } = options;
    
    // 创建下载器实例
    const downloader = isM3U8Url(url)
        ? new M3U8Downloader({ id, url, headers, dir, taskName, keyIV, deleteSegments })
        : new MP4Downloader({ id, url, headers, dir, taskName });

    // 设置下载器事件监听
    downloader.on('progress', lodash.throttle((progress) => {
        if (!downloadStateManager.activeTasks.has(id)) {
            return;
        }

        let statusCode, statusText;

        switch(progress.status) {
            case 'preparing':
                statusCode = DownloadStatus.Preparing;
                statusText = '准备中...';
                break;
            case 'parsing':
                statusCode = DownloadStatus.Parsing;
                statusText = '解析M3U8...';
                break;
            case 'downloading':
                statusCode = DownloadStatus.Downloading;
                statusText = `下载中... (${progress.current}/${progress.total})`;
                break;
            case 'merging':
                statusCode = DownloadStatus.Merging;
                const progressPercent = typeof progress.progress === 'number' ? progress.progress : 0;
                statusText = `合并中... (${progressPercent.toFixed(1)}%)`;
                break;
            case 'stopping':
                statusCode = DownloadStatus.Stopping;
                statusText = '停止中...';
                break;
            default:
                console.error('未知的下载进度状态：', progress.status);
        }
        
        const video = downloadStateManager.updateStatus(id, statusText, statusCode);
        if (video) {
            sendMessageToMainWindow('task-notify-update', video);
        }
    }, 500));
    
    downloader.on('stopped', () => {
        const video = downloadStateManager.updateStatus(id, '已停止', DownloadStatus.Stopped)
        downloadStateManager.removeTask(id);
        sendMessageToMainWindow('task-notify-end', video);
    });
    downloader.on('complete', ({ outputPath }) => completeTask(id, outputPath));
    downloader.on('error', (err) => {
        const video = downloadStateManager.failVideo(id, err);
        downloadStateManager.removeTask(id);
        sendMessageToMainWindow('task-notify-end', video);
    });
    
    if (downloader instanceof M3U8Downloader) {
        downloader.on('segment-error', ({segment, error}) => {
            logger.error(`片段下载失败: ${segment.uri}`, error);
        });
    }

    if (isNewTask) {
        // 创建新任务记录
        const video = downloadStateManager.addVideo({
            id,
            url,
            dir,
            time: dateFormat(new Date(), "yyyy-mm-dd HH:MM:ss"),
            status: '初始化...',
            statusCode: DownloadStatus.Init,
            isLiving: false,
            headers,
            taskName,
            success: true,
            videopath: '',
            downloadType: downloader instanceof M3U8Downloader ? 'm3u8' : 'mp4'
        });
        sendMessageToMainWindow('task-notify-create', video);
    }

    downloadStateManager.addTask(id, downloader);
    return downloader;
}

async function startDownload(object, iidx) {
    const id = object.id || (iidx != null ? (Date.now() + iidx) : Date.now());
    const taskName = (object.taskName || '').replace(taskNameFormatReg, "") || `${id}`;
    
    const downloader = initDownloader({
        id,
        url: object.url,
        headers: object.headers,
        dir: pathDownloadDir,
        taskName,
        keyIV: object.myKeyIV,
        deleteSegments: object.taskIsDelTs
    }, true);
    downloader.start();
}

function isM3U8Url(url) {
    return /\.m3u8($|\?)/i.test(url) || 
           (nconf.get('CustomUrlSuffix') && new RegExp(nconf.get('CustomUrlSuffix'), 'i').test(url));
}

function completeTask(id, filePath) {
    const video = downloadStateManager.completeVideo(id, filePath);
    if (video) {
        sendMessageToMainWindow('task-notify-end', video);
    }
    downloadStateManager.removeTask(id);
}

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

class FFmpegStreamReadable extends Readable {
    constructor(opt) {
        super(opt);
    }
    _read() { }
}

async function startDownloadLive(object) {
    let id = !object.id ? new Date().getTime() : object.id;
    let headers = object.headers;
    let taskName = (object.taskName || '').replace(taskNameFormatReg, "");
    let myKeyIV = object.myKeyIV;
    let url = object.url;
    if (!taskName) {
        taskName = `${id}`;
    }
    const dir = path.join(pathDownloadDir, taskName);

    logger.info(`下载直播到：${dir}`);

    if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, {
            recursive: true
        });
    }

    let count_downloaded = 0;
    let count_seg = 100;
    var video = downloadStateManager.addVideo({
        id: id,
        url: url,
        dir: dir,
        segment_total: count_seg,
        segment_downloaded: count_downloaded,
        time: dateFormat(new Date(), "yyyy-mm-dd HH:MM:ss"),
        status: '初始化...',
        statusCode: DownloadStatus.Init,
        isLiving: true,
        myKeyIV: myKeyIV,
        taskName: taskName,
        headers: headers,
        videopath: ''
    });

    if (!object.id) {
        sendMessageToMainWindow('task-notify-create', video);
    }

    let partent_uri = url.replace(/([^\/]*\?.*$)|([^\/]*$)/g, '');
    let segmentSet = new Set();
    let ffmpegInputStream = null;
    let ffmpegObj = null;
    globalCond[id] = true;
    while (globalCond[id]) {

        try {
            const response = await got(url, {
                headers: headers,
                timeout: httpTimeout,
                rejectUnauthorized: false,
                agent: proxy_agent
            }).catch(e => logger.error(`${url}下载出错：${e}`));
            if (response == null || response.body == null || response.body == '') {
                break;
            }
            let parser = new Parser();
            parser.push(response.body);
            parser.end();

            let count_seg = parser.manifest.segments.length;
            let segments = parser.manifest.segments;
            logger.info(`解析到 ${count_seg} 片段`)
            if (count_seg > 0) {
                //开始下载片段的时间，下载完毕后，需要计算下次请求的时间
                let _startTime = new Date();
                let _videoDuration = 0;
                for (let iSeg = 0; iSeg < segments.length; iSeg++) {
                    let segment = segments[iSeg];
                    if (segmentSet.has(segment.uri)) {
                        continue;
                    }
                    if (!globalCond[id]) {
                        break;
                    }
                    _videoDuration = _videoDuration + segment.duration * 1000;
                    let uri_ts = '';
                    if (/^http.*/.test(segment.uri)) {
                        uri_ts = segment.uri;
                    } else if (/^\/.*/.test(segment.uri)) {
                        let mes = url.match(/^https?:\/\/[^/]*/);
                        if (mes && mes.length >= 1) {
                            uri_ts = mes[0] + segment.uri;
                        } else {
                            uri_ts = partent_uri + segment.uri;
                        }
                    } else {
                        uri_ts = partent_uri + segment.uri;
                    }

                    let filename = `${((count_downloaded + 1) + '').padStart(6, '0')}.ts`;
                    let filpath = path.join(dir, filename);
                    let filpath_dl = path.join(dir, filename + ".dl");

                    for (let index = 0; index < 3; index++) {
                        if (!globalCond[id]) {
                            break;
                        }

                        //let tsStream = await got.get(uri_ts, {responseType:'buffer', timeout:httpTimeout ,headers:headers}).catch(logger.error).body();

                        await download(uri_ts, dir, {
                            filename: filename + ".dl",
                            timeout: httpTimeout,
                            rejectUnauthorized: false,
                            headers: headers,
                            agent: proxy_agent
                        }).catch((err) => {
                            logger.error(`${uri_ts}下载出错：${err}`)
                            // if (fs.existsSync(filpath_dl)) {
                            //     fs.unlinkSync(filpath_dl);
                            // }
                        });
                        if (fs.existsSync(filpath_dl)) {
                            let stat = fs.statSync(filpath_dl);
                            if (stat.size > 0) {
                                fs.renameSync(filpath_dl, filpath);
                            } else {
                                fs.unlinkSync(filpath_dl);
                            }
                        }
                        if (fs.existsSync(filpath)) {
                            segmentSet.add(segment.uri);
                            if (ffmpegObj == null) {
                                let outPathMP4 = path.join(dir, id + '.mp4');
                                let newid = id;
                                //不要覆盖之前下载的直播内容
                                while (fs.existsSync(outPathMP4)) {
                                    outPathMP4 = path.join(dir, newid + '.mp4');
                                    newid = newid + 1;
                                }
                                if (fs.existsSync(ffmpegPath)) {
                                    ffmpegInputStream = new FFmpegStreamReadable(null);

                                    ffmpegObj = new ffmpeg(ffmpegInputStream)
                                        .setFfmpegPath(ffmpegPath)
                                        .videoCodec('copy')
                                        .audioCodec('copy')
                                        .save(outPathMP4)
                                        .on('error', logger.info)
                                        .on('end', function () {
                                            video.videopath = outPathMP4;
                                            video.status = "已完成";
                                            sendMessageToMainWindow('task-notify-end', video);

                                            fs.writeFileSync(pathVideoDB, JSON.stringify(videoDatas));
                                        })
                                        .on('progress', logger.info);
                                } else {
                                    video.videopath = outPathMP4;
                                    video.status = "已完成，未发现本地FFMPEG，不进行合成。"
                                    video.statusCode = DownloadStatus.Success;
                                    sendMessageToMainWindow('task-notify-update', video);
                                }
                            }

                            if (ffmpegInputStream) {
                                ffmpegInputStream.push(fs.readFileSync(filpath));
                                fs.unlinkSync(filpath);
                            }

                            //fs.appendFileSync(path.join(dir,'index.txt'),`file '${filpath}'\r\n`);
                            count_downloaded = count_downloaded + 1;
                            video.segment_downloaded = count_downloaded;
                            downloadStateManager.updateStatus(id, `直播中... [${count_downloaded}]`, DownloadStatus.Downloading);
                            sendMessageToMainWindow('task-notify-update', downloadStateManager.getVideo(id));
                            break;
                        }
                    }
                }
                if (globalCond[id]) {
                    //使下次下载M3U8时间提前1秒钟。
                    _videoDuration = _videoDuration - 1000;
                    let _downloadTime = (new Date().getTime() - _startTime.getTime());
                    if (_downloadTime < _videoDuration) {
                        await sleep(_videoDuration - _downloadTime);
                    }
                }
            } else {
                break;
            }
            parser = null;
        } catch (error) {
            console.error('直播下载异常', error);
            logger.info(`直播下载异常：${error.response.body}`);
        }
    }
    if (ffmpegInputStream) {
        ffmpegInputStream.push(null);
    }

    if (count_downloaded <= 0) {
        downloadStateManager.failVideo(id, new Error("下载失败"));
        sendMessageToMainWindow('task-notify-end', downloadStateManager.getVideo(id));
        return;
    }
}


function formatTime(duration) {
    let sec = Math.floor(duration % 60).toLocaleString();
    let min = Math.floor(duration / 60 % 60).toLocaleString();
    let hour = Math.floor(duration / 3600 % 60).toLocaleString();
    if (sec.length != 2) sec = '0' + sec;
    if (min.length != 2) min = '0' + min;
    if (hour.length != 2) hour = '0' + hour;
    return hour + ":" + min + ":" + sec;
}


ipcMain.on('delvideo', function (event, id) {
    const video = downloadStateManager.getVideo(id);
    if (!video) {
        event.sender.send("delvideo-reply", {error: "视频不存在"});
        return;
    }

    try {
        // 停止相关下载任务
        const task = downloadStateManager.activeTasks.get(id);
        if (task) {
            task.stop();
            downloadStateManager.removeTask(id);
        }

        // 删除临时文件（成功时会清理，无需再删除）
        if (video.statusCode !== DownloadStatus.Success) {
            // MP4类型不处理
            if (video.downloadType === 'm3u8') {
                // 未下载完成时删除整个临时目录
                // 直接使用 video.taskName，因为它已经在初始化时处理过
                const tempDir = path.join(video.dir, video.taskName);
                if (fs.existsSync(tempDir)) {
                    fs.rmdirSync(tempDir, { recursive: true });
                }
            }
        }

        // 从状态管理中移除
        downloadStateManager.removeVideo(id);
        event.sender.send("delvideo-reply", {success: true, video});
    } catch (error) {
        console.error(error);
        logger.error(`删除视频出错：${error}`);
        event.sender.send("delvideo-reply", {
            error: error.message,
            video
        });
    }
});

function showDirInExploer(dir) {
    const stat = fs.statSync(dir);
    if (stat.isDirectory()) {
        shell.openExternal(dir).catch((reason) => {
            logger.error(`openExternal Error:${dir} ${reason}`);
    
            let files = fs.readdirSync(dir);
            if (files && files.length > 0) {
                shell.showItemInFolder(path.join(dir, files[0]));
            } else {
                shell.showItemInFolder(dir);
            }
        });
    } else {
        shell.showItemInFolder(dir);
    }
    
}

ipcMain.on('opendir', function (event, arg, path) {
    if (fs.existsSync(arg)) {
        showDirInExploer(arg);
    } else if (fs.existsSync(path)) {
        showDirInExploer(path)
    }
});

ipcMain.on('playvideo', function (event, { src, headers }) {
    if (src.startsWith('http') || nconf.get('config_player') === 'internal') {
        createPlayerWindow(src, headers);
    } else if (fs.existsSync(src)) {
        shell.openPath(src);
    }
});
ipcMain.on('StartOrStop', function (event, idStr) {
    let id = Number.parseInt(idStr);
    const video = downloadStateManager.getVideo(id);
    if (!video) {
        logger.info(`任务（${idStr}）不存在`);
        return;
    }


    if (isFinishStatus(video.statusCode)) {
        // 校验当前状态
        const isRunning = downloadStateManager.activeTasks.has(id);
        if (isRunning) {
            console.error('任务运行状态异常：', video);
            return;
        }
        try {
            // 重置任务状态
            downloadStateManager.updateStatus(id, "准备中...", DownloadStatus.Preparing);
            sendMessageToMainWindow('task-notify-update', video);

            // 创建并初始化下载器
            const downloader = initDownloader({
                id: video.id,
                url: video.url,
                headers: video.headers,
                dir: video.dir,
                taskName: video.taskName,
                keyIV: video.myKeyIV,
                deleteSegments: video.taskIsDelTs
            }, false);

            // 开始下载
            downloadStateManager.addTask(id, downloader);
            downloader.start();
        } catch (e) {
            downloadStateManager.updateStatus(id, "准备中...", DownloadStatus.Error);
            sendMessageToMainWindow('task-notify-update', video);
        }
    } else {
        // 停止下载
        downloadStateManager.stopTask(id);
    }
});

ipcMain.on('setting_isdelts', function (event, arg) {
    isdelts = arg;
});

ipcMain.on('set-config', function (event, data) {
    nconf.set(data.key, data.value);
    nconf.save();

    if (data.key == 'config_proxy') {
        const config_proxy = nconf.get('config_proxy');
        if (config_proxy) {
            proxy_agent = {
                http: new HttpProxyAgent({
                    keepAlive: true,
                    keepAliveMsecs: 1000,
                    maxSockets: 256,
                    maxFreeSockets: 256,
                    scheduling: 'lifo',
                    proxy: config_proxy
                }),
                https: new HttpsProxyAgent({
                    keepAlive: true,
                    keepAliveMsecs: 1000,
                    maxSockets: 256,
                    maxFreeSockets: 256,
                    scheduling: 'lifo',
                    proxy: config_proxy
                })
            };
            session.defaultSession.setProxy({
                mode: 'fixed_servers',
                proxyRules: config_proxy,
            })
        } else {
            session.defaultSession.setProxy({
                mode: 'auto_detect',
            });
        }
    } else if (data.key == 'downloadTsWithHeader') {
        downloadTsWithHeader = data.value;
    }
})

ipcMain.on('open-config-dir', function (event, arg) {
    let SaveDir = pathDownloadDir;
    logger.debug(`初始目录 ${SaveDir}`);
    dialog.showOpenDialog(mainWindow, {
        title: "请选择文件夹",
        defaultPath: SaveDir ? SaveDir : '',
        properties: ['openDirectory', 'createDirectory'],
    }).then(result => {
        if (!result.canceled && result.filePaths.length == 1) {
            logger.debug(`选择目录 ${result.filePaths}`);
            pathDownloadDir = result.filePaths[0];
            nconf.set('SaveVideoDir', pathDownloadDir);
            nconf.save();
            event.sender.send("message", {
                config_save_dir: pathDownloadDir,
                config_ffmpeg: ffmpegPath
            });
        }
    }).catch(err => {
        logger.error(`showOpenDialog ${err}`)
    });
});

ipcMain.on('open-select-m3u8', function (event, arg) {
    dialog.showOpenDialog(mainWindow, {
        title: "请选择一个M3U8文件",
        properties: ['openFile'],
    }).then(result => {
        if (!result.canceled && result.filePaths.length == 1) {
            event.sender.send("open-select-m3u8-reply", `file:///${result.filePaths[0]}`);
        }
    }).catch(err => {
        logger.error(`showOpenDialog ${err}`)
    });
});

ipcMain.on('open-select-ts-dir', function (event, arg) {
    if (arg) {
        let files = [];
        try {
            files = fs.readdirSync(result.filePaths[0])
        } catch (error) {

        }
        if (files && files.length > 0) {
            let _files = files.filter((f) => {
                return f.endsWith('.ts') || f.endsWith('.TS')
            });
            if (_files.length) {
                event.sender.send("open-select-ts-select-reply", _files);
                return;
            }
        }
        return;
    }
    dialog.showOpenDialog(mainWindow, {
        title: "请选择欲合并的TS文件",
        properties: ['openFile', 'multiSelections'],
        filters: [{
            name: '视频片段',
            extensions: ['ts']
        }, {
            name: '所有文件',
            extensions: ['*']
        }]
    }).then(result => {
        if (!result.canceled && result.filePaths.length >= 1) {
            if (result.filePaths.length == 1) {
                event.sender.send("open-select-ts-dir-reply", result.filePaths[0]);

                let files = [];
                try {
                    files = fs.readdirSync(result.filePaths[0])
                } catch (error) {

                }
                if (files && files.length > 0) {
                    let _files = files.filter((f) => {
                        return f.endsWith('.ts') || f.endsWith('.TS')
                    });
                    if (_files && _files.length) {
                        event.sender.send("open-select-ts-select-reply", _files);
                        return;
                    } else {
                        event.sender.send("open-select-ts-select-reply", files);
                        return;
                    }
                }
            }
            let _files = result.filePaths.filter((f) => {
                return f.endsWith('.ts') || f.endsWith('.TS')
            });
            if (_files && _files.length) {
                event.sender.send("open-select-ts-select-reply", _files);
            } else {
                event.sender.send("open-select-ts-select-reply", result.filePaths);
            }
        }
    }).catch(err => {
        logger.error(`showOpenDialog ${err}`)
    });
});

ipcMain.on('start-merge-ts', async function (event, task) {
    if (!task) return
    let name = task.name ? task.name : (new Date().getTime() + '');

    let dir = path.join(pathDownloadDir, name);
    if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, {
            recursive: true
        });
    }
    let outPathMP4 = path.join(dir, `${new Date().getTime()}.mp4`);

    if (fs.existsSync(ffmpegPath)) {
        sendMessageToMainWindow('start-merge-ts-status', {
            code: 0,
            progress: 1,
            status: '开始合并...'
        });
        ffmpegInputStream = new FFmpegStreamReadable(null);

        let ffmpegObj = new ffmpeg(ffmpegInputStream)
            .setFfmpegPath(ffmpegPath)
            .videoCodec(task.mergeType == 'speed' ? 'copy' : 'libx264')
            .audioCodec(task.mergeType == 'speed' ? 'copy' : 'aac')
            .format('mp4')
            .save(outPathMP4)
            .on('error', (error) => {
                logger.error(`合并出错：${error}`)
                sendMessageToMainWindow('start-merge-ts-status', {
                    code: -2,
                    progress: 100,
                    status: '合并出错|' + error
                });
            })
            .on('end', function () {
                logger.info(`${outPathMP4} merge finished.`)
                sendMessageToMainWindow('start-merge-ts-status', {
                    code: 1,
                    progress: 100,
                    status: 'success',
                    dir: dir,
                    path: outPathMP4
                });
            })
            .on('progress', (info) => {
                logger.info(JSON.stringify(info));
                sendMessageToMainWindow('start-merge-ts-status', {
                    code: 0,
                    progress: -1,
                    status: JSON.stringify(info)
                });
            });
        let count = task.ts_files.length
        let _last = '';
        for (let index = 0; index < count; index++) {
            const file = task.ts_files[index];
            ffmpegInputStream.push(fs.readFileSync(file));
            while (ffmpegInputStream._readableState.length > 0) {
                await sleep(200);
            }
            let precent = Number.parseInt((index + 1) * 100 / count);
            sendMessageToMainWindow('start-merge-ts-status', {
                code: 0,
                progress: precent,
                status: `合并中...[${precent}%]`
            });
        }
        ffmpegInputStream.push(null);
    } else {
        sendMessageToMainWindow('start-merge-ts-status', {
            code: -1,
            progress: 100,
            status: '未检测到FFMPEG,不进行合并操作。'
        });
    }
});
